package com.tjbklx1.redis.chap06;

import java.util.List;
import java.util.UUID;

import com.tjbklx1.redis.JedisUtil;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;

/**
 * 分布式锁
 * @author tjbklx1
 * @date 2018-12-12
 */
public class App63_DistributedLock {

	public static void main(String[] args) throws Exception {
		new App63_DistributedLock().run();
	}

	private void run() throws Exception {
		Jedis conn = JedisUtil.getJedisWithDB();
		testDistributedLocking(conn);
	}   

	public void testDistributedLocking(Jedis conn) throws InterruptedException {
		System.out.println("\n----- testDistributedLocking -----");
		conn.del("lock:testlock");
		System.out.println("Getting an initial lock...");
		assert acquireLockWithTimeout(conn, "testlock", 1000, 1000) != null;
		System.out.println("Got it!");
		System.out.println("Trying to get it again without releasing the first one...");
		assert acquireLockWithTimeout(conn, "testlock", 10, 1000) == null;
		System.out.println("Failed to get it!");
		System.out.println();

		System.out.println("Waiting for the lock to timeout...");
		Thread.sleep(2000);
		System.out.println("Getting the lock again...");
		String lockId = acquireLockWithTimeout(conn, "testlock", 1000, 1000);
		assert lockId != null;
		System.out.println("Got it!");
		System.out.println("Releasing the lock...");
		assert releaseLock(conn, "testlock", lockId);
		System.out.println("Released it...");
		System.out.println();

		System.out.println("Acquiring it again...");
		assert acquireLockWithTimeout(conn, "testlock", 1000, 1000) != null;
		System.out.println("Got it!");
		conn.del("lock:testlock");
	}

	public static String acquireLock(Jedis conn, String lockName) {
		return acquireLock(conn, lockName, 10000);
	}

	public static String acquireLock(Jedis conn, String lockName, long acquireTimeout) {
		String identifier = UUID.randomUUID().toString();

		//重点
		long end = System.currentTimeMillis() + acquireTimeout;
		while (System.currentTimeMillis() < end) {
			//如果setnx 成功 才返回 identifier
			if (conn.setnx("lock:" + lockName, identifier) == 1) {
				return identifier;
			}

			try {
				Thread.sleep(1);
			} catch (InterruptedException ie) {
				Thread.currentThread().interrupt();
			}
		}

		return null;
	}

	public static String acquireLockWithTimeout(Jedis conn, String lockName, long acquireTimeout, long lockTimeout) {
		String identifier = UUID.randomUUID().toString();
		String lockKey = "lock:" + lockName;
		int lockExpire = (int) (lockTimeout / 1000);

		long end = System.currentTimeMillis() + acquireTimeout;
		while (System.currentTimeMillis() < end) {
			if (conn.setnx(lockKey, identifier) == 1) {
				conn.expire(lockKey, lockExpire);
				return identifier;
			}
			//当 key 存在但没有设置剩余生存时间时, 设置超时
			if (conn.ttl(lockKey) == -1) {
				conn.expire(lockKey, lockExpire);
			}

			try {
				Thread.sleep(1);
			} catch (InterruptedException ie) {
				Thread.currentThread().interrupt();
			}
		}

		// null indicates that the lock was not acquired
		return null;
	}

	public static boolean releaseLock(Jedis conn, String lockName, String identifier) {
		String lockKey = "lock:" + lockName;

		while (true) {
			conn.watch(lockKey);
			if (identifier.equals(conn.get(lockKey))) {
				Transaction trans = conn.multi();
				//利用事务删除LOCK_KEY ,如果不成功继续删除
				trans.del(lockKey);
				List<Object> results = trans.exec();
				if (results == null) {
					continue;
				}
				return true;
			}
			// 最好UNwatch
			conn.unwatch();
			break;
		}

		return false;
	}
}
